﻿namespace BM.Tools.WebControls.MForm.ScriptReferences
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Web;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Xml.XPath;

    /// <summary>
    /// The class is serving as the way to call a script service from the server
    /// </summary>
    public class ScriptServiceInvoker
    {
        /// <summary>
        /// a content type
        /// </summary>
        private string contentType = "application/x-www-form-urlencoded";

        /// <summary>
        /// a HTTP method
        /// </summary>
        private string method = "POST";

        /// <summary>
        /// a dictionary of sent parameters
        /// </summary>
        private Dictionary<string, string> parameters = new Dictionary<string, string>();

        /// <summary>
        /// Initializes a new instance of the <see cref="ScriptServiceInvoker"/> class.         
        /// </summary>
        /// <param name="servicePath">
        /// a path to the service
        /// </param>
        /// <param name="serviceMethod">
        /// a service method name
        /// </param>
        public ScriptServiceInvoker(string servicePath, string serviceMethod)
        {
            ServicePath = servicePath;
            ServiceMethod = serviceMethod;
        }

        /// <summary>
        /// Gets or sets ContentType.
        /// </summary>
        /// <value>
        /// The content type.
        /// </value>
        [DefaultValue("application/x-www-form-urlencoded")]
        public string ContentType
        {
            get
            {
                return contentType;
            }

            set
            {
                contentType = value;
            }
        }

        /// <summary>
        /// Gets or sets HTTP Method.
        /// </summary>
        /// <value>
        /// The HTTP method.
        /// </value>
        [DefaultValue("POST")]
        public string Method
        {
            get
            {
                return method;
            }

            set
            {
                method = value;
            }
        }

        /// <summary>
        /// Gets the text that reflectects the parameters keys and values 
        /// </summary>
        /// <value>
        /// The parameters text.
        /// </value>
        public string ParametersText
        {
            get
            {
                var parameterChunks = parameters.Select(x => string.Concat(x.Key, "=", x.Value));
                return String.Join("&", parameterChunks.ToArray());
            }
        }

        /// <summary>
        /// Gets or sets the service method name
        /// </summary>
        public string ServiceMethod
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the path to the service
        /// </summary>
        public string ServicePath
        {
            get;
            set;
        }

        /// <summary>
        /// adds a parameter to the parameters dictionary
        /// </summary>
        /// <param name="name">
        /// The parameter name.
        /// </param>
        /// <param name="value">
        /// The parameter value.
        /// </param>
        public void AddParameter(string name, string value)
        {
            parameters.Add(name, value);
        }

        /// <summary>
        /// Invokes a web request with the given settings and returns the web response
        /// </summary>
        /// <returns>
        /// the web response
        /// </returns>
        [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The string does not represent an Uri")]
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "the operation may be expensive or calling the member twice in succession may create different results")]
        public WebResponse GetResponse()
        {
            var uri = new Uri(HttpContext.Current.Request.Url, ServicePath + "/" + ServiceMethod);
            WebRequest request = WebRequest.Create(uri);
            request.Method = Method;
            request.ContentType = ContentType;
            using (var sw = new StreamWriter(request.GetRequestStream()))
            {
                try
                {
                    sw.Write(ParametersText);
                    sw.Flush();
                    sw.Close();
                    return request.GetResponse();
                }
                catch (WebException e)
                {
                    return e.Response;
                }
            }
        }

        /// <summary>
        /// returns a value from the web response
        /// </summary>
        /// <typeparam name="T">
        /// the type of the value
        /// </typeparam>
        /// <returns>
        /// a value from the web response
        /// </returns>
        /// <exception cref="ScriptServiceInvokerException">
        /// if anything with the script service went wrong.
        /// </exception>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "There is nothing difficult abut this method. It needs the type to provide a type-safe return")]
        public T GetValue<T>()
        {
            try
            {
                WebResponse response = GetResponse();
                using (XmlReader xr = XmlReader.Create(response.GetResponseStream()))
                {
                    var document = new XPathDocument(xr);
                    var xs = new XmlSerializer(typeof(T), GetRootNamespace(document));
                    return (T)xs.Deserialize(document.CreateNavigator().ReadSubtree());
                }
            }
            catch (InvalidOperationException e)
            {
                throw new ScriptServiceInvokerException("The value obtaining failed.", e);
            }
            catch (XmlException xe)
            {
                throw new ScriptServiceInvokerException("The value obtaining failed.", xe);
            }
        }

        /// <summary>
        /// returns the namespace of the xml root
        /// </summary>
        /// <param name="document">
        /// the document in which the namespace is checked
        /// </param>
        /// <returns>
        /// the namespace
        /// </returns>
        private static string GetRootNamespace(XPathDocument document)
        {
            XPathNavigator navigator = document.CreateNavigator();
            navigator.MoveToChild(XPathNodeType.Element);
            return navigator.NamespaceURI;
        }
    }
}